define(['THREE.CSS2DRenderer', 'mapboxgl', 'mbCameraSync'], function (CSS2DRenderer, mapboxgl, mbCameraSync) {

    var $mb = {};

    function drawTooltip(tooltipText, mapboxStyle = false) {
        if (tooltipText) {
            var divToolTip;
            if (mapboxStyle) {
                var divContent = document.createElement('div');
                divContent.className = 'mapboxgl-popup-content';
                var strong = document.createElement('strong');
                strong.innerHTML = tooltipText;
                divContent.appendChild(strong);
                var tip = document.createElement('div');
                tip.className = 'mapboxgl-popup-tip';
                var div = document.createElement('div');
                div.className = 'marker mapboxgl-popup-anchor-bottom';
                div.appendChild(tip);
                div.appendChild(divContent);
                divToolTip = document.createElement('div');
                divToolTip.className += 'label3D';
                divToolTip.appendChild(div);
            } else {
                divToolTip = document.createElement('span');
                // divToolTip.className = this._defaults.tooltip.cssClass;
                divToolTip.innerHTML = tooltipText;
            }
            return divToolTip;
        }
    }

    $mb.drawTooltip = drawTooltip;

    function formatLngLat(lng, lat) {

        if (lng[0] && lng[1]) {
            lat = lng[1];
            lng = lng[0];
        }

        if (lng.lng && lng.lat) {
            lat = lng.lat;
            lng = lng.lng;
        }

        return [lng, lat];
    }

    function mercator3dLayer(opts) {

        opts = opts || {};

        // example code

        // parameters to ensure the model is georeferenced correctly on the map
        var modelOrigin = [120, 30];
        var modelAltitude = 0;
        var modelRotate = [Math.PI / 2, 0, 0];

        var modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
            modelOrigin,
            modelAltitude
        );

        // transformation parameters to position, rotate and scale the 3D model onto the map
        var modelTransform = {
            translateX: modelAsMercatorCoordinate.x,
            translateY: modelAsMercatorCoordinate.y,
            translateZ: modelAsMercatorCoordinate.z,
            rotateX: modelRotate[0],
            rotateY: modelRotate[1],
            rotateZ: modelRotate[2],
            /* Since our 3D model is in real world meters, a scale transform needs to be
             * applied since the CustomLayerInterface expects units in MercatorCoordinates.
             */
            scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
        };

        return {
            id: opts.layerId,
            type: 'custom',
            renderingMode: '3d',
            onAdd: function (map, gl) {
                this.camera = new THREE.Camera();
                this.scene = new THREE.Scene();

                // create two three.js lights to illuminate the model
                var directionalLight = new THREE.DirectionalLight(0xffffff);
                directionalLight.position.set(0, -70, 100).normalize();
                this.scene.add(directionalLight);

                var directionalLight2 = new THREE.DirectionalLight(0xffffff);
                directionalLight2.position.set(0, 70, 100).normalize();
                this.scene.add(directionalLight2);

                this.map = map;

                var rendererOpts = {
                    antialias: true,
                    alpha: true,
                    preserveDrawingBuffer: !!opts.enableSnapshot,
                };

                if (opts.renderOpts) {
                    rendererOpts = Object.assign(rendererOpts, opts.renderOpts || {});
                }

                rendererOpts.canvas = map.getCanvas();
                rendererOpts.context = gl;

                // use the Mapbox GL JS map canvas for three.js
                this.renderer = new THREE.WebGLRenderer(rendererOpts);
                this.renderer.autoClear = false;

                /*
                 * TODO: listen on resize events
                 */

                //create the renderer
                this.popupRenderer = new THREE.CSS2DRenderer();
                this.popupRenderer.setSize(this.map.getCanvas().clientWidth, this.map.getCanvas().clientHeight);
                this.popupRenderer.domElement.style.position = 'absolute';
                this.popupRenderer.domElement.id = opts.canvasId;
                this.popupRenderer.domElement.style.top = 0;
                this.map.getCanvasContainer().appendChild(this.popupRenderer.domElement);

                this.markerLayer = new THREE.Group();
                this.scene.add(this.markerLayer);

            },
            _setCameraStatus: function (modelOrigin, modelAltitude, modelRotate, gl, matrix) {

                /*
                 * TODO: free up camera for controling
                 */

                var rotationX = new THREE.Matrix4().makeRotationAxis(
                    new THREE.Vector3(1, 0, 0),
                    modelTransform.rotateX
                );
                var rotationY = new THREE.Matrix4().makeRotationAxis(
                    new THREE.Vector3(0, 1, 0),
                    modelTransform.rotateY
                );
                var rotationZ = new THREE.Matrix4().makeRotationAxis(
                    new THREE.Vector3(0, 0, 1),
                    modelTransform.rotateZ
                );

                var m = new THREE.Matrix4().fromArray(matrix);
                var l = new THREE.Matrix4()
                    .makeTranslation(
                        modelTransform.translateX,
                        modelTransform.translateY,
                        modelTransform.translateZ
                    )
                    .scale(
                        new THREE.Vector3(
                            modelTransform.scale,
                            -modelTransform.scale,
                            modelTransform.scale
                        )
                    )
                    .multiply(rotationX)
                    .multiply(rotationY)
                    .multiply(rotationZ);

                this.camera.projectionMatrix = m.multiply(l);
            },
            render: function (gl, matrix) {
                this.renderInMbmap(this.map, gl, matrix);
            },
            renderInMbmap: function (bmap, gl, matrix) {
                var self = this;
                if (!bmap) return;
                mbCameraSync.beforeLookAt(bmap, function (lngLat, meterPerPx, val, rotation, pitch, fov) {
                    lngLat = formatLngLat(lngLat); // [lng, lat]
                    var elevation = 0;
                    // TODO: rotate, pitch, ?
                    var rotate = [Math.PI / 2, rotation, 0];
                    self._setCameraStatus(lngLat, elevation, rotate, gl, matrix);
                    self.renderer.state.reset();
                    self.renderer.render(self.scene, self.camera);
                    self.popupRenderer.render(self.scene, self.camera);
                    self.map.triggerRepaint();
                });
            },
            addMarkerElem: function (marker) {
                // let divToolTip = drawTooltip("Custom Popup with Altitude", true);
                let popupAlt = new THREE.CSS2DObject(marker.element);
                marker.css2dobject = popupAlt;
                popupAlt.position.set(marker.offsetX || 0, marker.elevation, marker.offsetY || 0);
                this.markerLayer.add(popupAlt);
            },
        };
    }

    $mb.mercator3dLayer = mercator3dLayer;

    return $mb;

});